﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace xbfengine
{
    public delegate void XbfMachineOutputDelegate(char theCharacter);
    public delegate char XbfMachineInputDelegate();
    public class XbfMachine
    {
        enum Tokens
        {
            Increment,
            Decrement,
            Next,
            Previous,
            StartLoop,
            EndLoop,
            Output,
            Input
        }
        private XbfMachineInputDelegate inputDelegate=null;
        private XbfMachineOutputDelegate outputDelegate=null;
        public XbfMachineInputDelegate InputDelegate
        {
            get
            {
                return inputDelegate;
            }
            set
            {
                inputDelegate = value;
            }
        }
        public XbfMachineOutputDelegate OutputDelegate
        {
            get
            {
                return outputDelegate;
            }
            set
            {
                outputDelegate = value;
            }
        }
        private int memoryPointer;
        private int instructionPointer;
        private List<char> memory = new List<char>();
        private List<Tokens> program = new List<Tokens>();
        private static Dictionary<Tokens, Tokens> opposites = new Dictionary<Tokens, Tokens>();
        private Dictionary<string, XElement> procedures = new Dictionary<string, XElement>();
        private Dictionary<string, int> GetElementParameters(XElement element)
        {
            Dictionary<string, int> dummy = new Dictionary<string, int>();
            return GetElementParameters(element, dummy);
        }
        private Dictionary<string, int> GetElementParameters(XElement element, Dictionary<string, int> parameters)
        {
            Dictionary<string, int> result = new Dictionary<string, int>(parameters);
            foreach (XAttribute attribute in element.Attributes())
            {
                if (attribute.Name.LocalName == "id") continue;
                int value = 0;
                if (int.TryParse(attribute.Value, out value))
                {
                    result[attribute.Name.LocalName] = value;
                }
                else
                {
                    result[attribute.Name.LocalName] = parameters[attribute.Value];
                }
            }
            return result;
        }
        private void AddTokenToTokenList(List<Tokens> tokenList, Tokens token)
        {
            if (tokenList.Count > 0)
            {
                if (opposites.ContainsKey(token))
                {
                    if (tokenList.Last() == opposites[token])
                    {
                        tokenList.RemoveAt(tokenList.Count - 1);
                    }
                    else
                    {
                        tokenList.Add(token);
                    }
                }
                else
                {
                    tokenList.Add(token);
                }
            }
            else
            {
                tokenList.Add(token);
            }
        }
        private void AddTokenListToTokenList(List<Tokens> destination, List<Tokens> source)
        {
            foreach (Tokens token in source)
            {
                AddTokenToTokenList(destination,token);
            }
        }
        private void AddTokenToProgram(Tokens token)
        {
            AddTokenToTokenList(program, token);
        }
        private void AddTokenListToProgram(List<Tokens> tokenList)
        {
            AddTokenListToTokenList(program, tokenList);
        }
        private List<Tokens> SubElementsToTokenList(XElement parent, Dictionary<string, int> parameters, int varindex)
        {
            List<Tokens> result = new List<Tokens>();
            foreach (XElement element in parent.Elements())
            {
                int count = 1;
                if (element.HasAttributes && element.Attribute("count") != null)
                {
                    if (!int.TryParse(element.Attribute("count").Value, out count))
                    {
                        count = parameters[element.Attribute("count").Value];
                    }
                }
                Tokens token;
                if (element.Name == "var")
                {
                    parameters[element.Attribute("id").Value] = varindex++;
                    continue;
                }
                else if (element.Name == "proc")
                {
                    procedures[element.Attribute("id").Value] = element;
                    continue;
                }
                else if (element.Name == "do")
                {
                    token = Tokens.StartLoop;
                }
                else if (element.Name == "loop")
                {
                    token = Tokens.EndLoop;
                }
                else if (element.Name == "inc")
                {
                    token = Tokens.Increment;
                }
                else if (element.Name == "dec")
                {
                    token = Tokens.Decrement;
                }
                else if (element.Name == "next")
                {
                    token = Tokens.Next;
                }
                else if (element.Name == "prev")
                {
                    token = Tokens.Previous;
                }
                else if (element.Name == "in")
                {
                    token = Tokens.Input;
                }
                else if (element.Name == "out")
                {
                    token = Tokens.Output;
                }
                else if (procedures.ContainsKey(element.Name.LocalName))
                {
                    AddTokenListToTokenList(result,SubElementsToTokenList(procedures[element.Name.LocalName], GetElementParameters(element, GetElementParameters(procedures[element.Name.LocalName], parameters)), varindex));
                    continue;
                }
                else
                {
                    continue;
                }
                if (count < 0 && opposites.ContainsKey(token))
                {
                    token = opposites[token];
                    count *= -1;
                }
                while (count > 0)
                {
                    AddTokenToTokenList(result, token);
                    count--;
                }
            }
            return result;
        }
        public void LoadFromFile(string filename)
        {
            program.Clear();
            XDocument doc = XDocument.Load(filename);
            AddTokenListToProgram(SubElementsToTokenList(doc.Element("xbf"), GetElementParameters(doc.Element("xbf")),0));
        }
        public void Reset()
        {
            memory.Clear();
            instructionPointer = 0;
            memoryPointer = 0;
        }
        private char ReadMemory(int theMemoryLocation)
        {
            while (theMemoryLocation >= memory.Count)
            {
                memory.Add((char)0);
            }
            return memory[theMemoryLocation];
        }
        private void WriteMemory(int theMemoryLocation, char value)
        {
            while (theMemoryLocation >= memory.Count)
            {
                memory.Add((char)0);
            }
            memory[theMemoryLocation]=value;
        }
        public void Step()
        {
            if (IsRunning())
            {
                switch (program[instructionPointer])
                {
                    case Tokens.Increment:
                        WriteMemory(memoryPointer, (char)(ReadMemory(memoryPointer) + 1));
                        instructionPointer++;
                        break;
                    case Tokens.Decrement:
                        WriteMemory(memoryPointer, (char)(ReadMemory(memoryPointer) - 1));
                        instructionPointer++;
                        break;
                    case Tokens.Next:
                        memoryPointer++;
                        instructionPointer++;
                        break;
                    case Tokens.Previous:
                        memoryPointer--;
                        instructionPointer++;
                        break;
                    case Tokens.Output:
                        if (outputDelegate != null)
                        {
                            outputDelegate(ReadMemory(memoryPointer));
                        }
                        instructionPointer++;
                        break;
                    case Tokens.Input:
                        if (inputDelegate != null)
                        {
                            WriteMemory(memoryPointer, inputDelegate());
                        }
                        instructionPointer++;
                        break;
                    case Tokens.StartLoop:
                        if (ReadMemory(memoryPointer) == (char)0)
                        {
                            int counter=0;
                            do
                            {
                                switch(program[instructionPointer])
                                {
                                    case Tokens.StartLoop:
                                        counter++;
                                        break;
                                    case Tokens.EndLoop:
                                        counter--;
                                        break;
                                }
                                instructionPointer++;
                            }
                            while(counter>0);
                        }
                        else
                        {
                            instructionPointer++;
                        }
                        break;
                    case Tokens.EndLoop:
                        if (ReadMemory(memoryPointer) != (char)0)
                        {
                            int counter = 1;
                            do
                            {
                                instructionPointer--;
                                switch (program[instructionPointer])
                                {
                                    case Tokens.StartLoop:
                                        counter--;
                                        break;
                                    case Tokens.EndLoop:
                                        counter++;
                                        break;
                                }
                            }
                            while (counter > 0);
                        }
                        else
                        {
                            instructionPointer++;
                        }
                        break;
                }
            }
        }
        public bool IsRunning()
        {
            return (instructionPointer>=0) && (instructionPointer < program.Count);
        }
        public void Execute()
        {
            Reset();
            while (IsRunning())
            {
                Step();
            }
        }
        public XbfMachine()
        {
        }
        static XbfMachine()
        {
            opposites[Tokens.Decrement] = Tokens.Increment;
            opposites[Tokens.Increment] = Tokens.Decrement;
            opposites[Tokens.Next] = Tokens.Previous;
            opposites[Tokens.Previous] = Tokens.Next;
        }
    }
}
